/*
	Copyright (C) 2008 Christopher E. Miller
	
	This software is provided 'as-is', without any express or implied
	warranty.  In no event will the authors be held liable for any damages
	arising from the use of this software.
	
	Permission is granted to anyone to use this software for any purpose,
	including commercial applications, and to alter it and redistribute it
	freely, subject to the following restrictions:
	
	1. The origin of this software must not be misrepresented; you must not
	   claim that you wrote the original software. If you use this software
	   in a product, an acknowledgment in the product documentation would be
	   appreciated but is not required.
	2. Altered source versions must be plainly marked as such, and must not be
	   misrepresented as being the original software.
	3. This notice may not be removed or altered from any source distribution.
*/


/*  gcc -Wl,-E -shared -fPIC -rdynamic -o irclyserv.so -g irclyserv.c */


#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>


void* server_malloc(size_t len);
void* server_realloc(void* p, size_t len);
void server_free(void* p);

typedef struct _SockInfo
{
	int socket;
	unsigned long flags;
	char* name;
	void* data;
	size_t datalen;
	int (*callback)(struct _SockInfo*, unsigned id, void* cinfo);
	char* sendbuf;
	size_t sendlen;
	char* nextsend;
	char* recvbuf;
	size_t recvlen;
	char* nextrecv;
}SockInfo;

int socket_register(int socket, unsigned long flags, char* name,
	int (*callback)(SockInfo*, unsigned id, void* cinfo));
void socket_unregister(int socket);

size_t socket_get_all(void (*callback)(SockInfo*, void* extra), void* extra);
size_t socket_get_array(SockInfo*** pinfos);
int socket_set_callback(int socket,
	int (*callback)(SockInfo*, unsigned id, void* cinfo));
void* socket_alloc_data(int socket, size_t datalen);
void* socket_get_data(int socket, size_t* pdatalen);

SockInfo* socket_info(int socket);
int socket_state(int socket);
char* socket_recv(int socket, int* len, unsigned flags);
int socket_send(int socket, const void* buf, int len, unsigned flags);
void socket_close(int socket);
int socket_select(int nfds, fd_set* reads, fd_set* writes, fd_set* errors, struct timeval* ptv);


typedef struct _SockList
{
	int* socks;
	size_t len;
}SockList;


int socklist_add(SockList* slist, int sock)
{
	int* newsocks;
	
	newsocks = server_realloc(slist->socks, sizeof(int*) * (slist->len + 1));
	if(!newsocks)
		return 0;
	
	newsocks[slist->len] = sock;
	slist->socks = newsocks;
	slist->len++;
	
	return 1;
}


void socklist_del(SockList* slist, int sock)
{
	size_t iw;
	
	for(iw = 0; iw != slist->len; iw++)
	{
		if(sock == slist->socks[iw])
		{
			if(iw != slist->len)
				slist->socks[iw] = slist->socks[slist->len - 1]; /* Doesn't preserve order. */
			slist->len--;
			slist->socks = server_realloc(slist->socks, sizeof(int*) * slist->len); /* Nev'r fail! (TM) */
			if(!slist->socks)
				slist->len = 0;
			break;
		}
	}
}


/* Warning: ONLY add to the END of these structs! or the service cannot be reloaded. */


typedef struct _Session
{
	size_t len;
	SockList usersockets;
	SockList ircsockets;
	char* name;
	unsigned sessionid;
}Session;

Session Session_INIT = { 0, { NULL, 0}, { NULL, 0 }, NULL, 1 };


typedef struct _UserData
{
	Session* session;
	int usersocket;
}UserData;

UserData UserData_INIT = { NULL, -1 };


typedef struct _IrcData
{
	Session* session;
	char* name;
	int ircsocket;
}IrcData;

IrcData IrcData_INIT = { NULL, NULL, -1 };


Session* session_ensure(Session* session)
{
	size_t oldlen;
	
	oldlen = 0;
	if(session)
		oldlen = session->len;
	
	if(oldlen < sizeof(Session))
	{
		session = server_realloc(session, sizeof(Session));
		if(session)
		{
			memcpy(((char*)session) + oldlen, ((char*)&Session_INIT) + oldlen, sizeof(Session) - oldlen);
			session->len = sizeof(Session);
		}
	}
	return session;
}


void session_release(Session* session)
{
	server_free(session->usersockets.socks);
	server_free(session->ircsockets.socks);
	
	server_free(session->name);
	
	server_free(session);
}


UserData* userdata_ensure(int usersock)
{
	UserData* udata;
	size_t oldlen;
	
	oldlen = 0;
	udata = socket_get_data(usersock, &oldlen);
	
	if(oldlen < sizeof(UserData))
	{
		udata = socket_alloc_data(usersock, sizeof(UserData));
		if(udata)
		{
			memcpy(((char*)udata) + oldlen, ((char*)&UserData_INIT) + oldlen, sizeof(UserData) - oldlen);
			
			if(udata->session)
			{
				udata->session = session_ensure(udata->session);
			}
		}
	}
	return udata;
}


void userdata_release(UserData* udata)
{
}


IrcData* ircdata_ensure(int ircsock)
{
	IrcData* idata;
	size_t oldlen;
	
	oldlen = 0;
	idata = socket_get_data(ircsock, &oldlen);
	
	if(oldlen < sizeof(IrcData))
	{
		idata = socket_alloc_data(ircsock, sizeof(IrcData));
		if(idata)
		{
			memcpy(((char*)idata) + oldlen, ((char*)&IrcData_INIT) + oldlen, sizeof(IrcData) - oldlen);
			
			if(idata->session)
			{
				idata->session = session_ensure(idata->session);
			}
		}
	}
	return idata;
}


void ircdata_release(IrcData* udata)
{
	server_free(udata->name);
}


int lsock = -1;


static int _ircsocket_callback(SockInfo* pinfo, unsigned id, void* cinfo)
{
	int* pi;
	
	pi = cinfo;
	
#if 0
	UserData* udata;
	IrcData* idata;
	
	if(1 == id)
	{
		/* Read... */
		
		idata = pinfo->data;
		if(-1 != idata->usersocket)
		{
			udata = socket_info(idata->usersocket);
			if(udata)
			{
				
			}
		}
	}
#endif
	
	return 0;
}


static int _usersocket_callback(SockInfo* pinfo, unsigned id, void* cinfo)
{
	return 0;
}


static int serv(void)
{
	char buf[1024];
	SockInfo** infos;
	SockInfo* info;
	UserData* udata;
	IrcData* idata;
	char* cs;
	size_t ninfos, iw, iwsock;
	struct sockaddr_in sin;
	fd_set reads, writes;
	int nfds = 0;
	int nsock;
	int i;
	
	FD_ZERO(&reads);
	FD_ZERO(&writes);
	
	if(lsock > nfds)
		nfds = lsock;
	FD_SET(lsock, &reads);
	
	ninfos = socket_get_array(&infos);
	
	if(socket_select(nfds + 1, &reads, &writes, NULL, NULL) > 0)
	{
		if(FD_ISSET(lsock, &reads))
		{
			/* New user! */
			nsock = accept(lsock, NULL, NULL);
			if(-1 != nsock)
			{
				if(socket_register(nsock, 2, "user", _usersocket_callback))
				{
					udata = userdata_ensure(nsock);
					if(udata)
					{
						udata->usersocket = nsock;
						
						/*socket_send(nsock, "Welcome\r\n", 9, 0);*/
					}
					else
					{
						fprintf(stderr, "Unable to allocate data for new \"user\"\n");
						close(nsock);
						socket_unregister(nsock);
					}
				}
				else
				{
					fprintf(stderr, "Unable to register new \"user\"\n");
					close(nsock);
				}
			}
		}
	}
	
	return 1; /* I called select(). */
}


static void _sockstart(SockInfo* pinfo, void* extra)
{
	/* To-do: fire callback for read/write if data in buffer, so unprocessed data is processed... */
	
	if(!strcmp(pinfo->name, "irc"))
	{
		ircdata_ensure(pinfo->socket);
		socket_set_callback(pinfo->socket, _ircsocket_callback);
	}
	else if(!strcmp(pinfo->name, "user"))
	{
		userdata_ensure(pinfo->socket);
		socket_set_callback(pinfo->socket, _usersocket_callback);
	}
}


static void _sockstop(SockInfo* pinfo, void* extra)
{
	if(!strcmp(pinfo->name, "irc"))
	{
		socket_set_callback(pinfo->socket, NULL);
	}
}


static int do_load(void)
{
	struct sockaddr_in sin;
	int i;
	
	lsock = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == lsock)
	{
		fprintf(stderr, "Unable to create socket (lsock)\n");
		return 0;
	}
	
	i = fcntl(lsock, F_GETFL, 0);
	if(-1 == i || -1 == fcntl(lsock, F_SETFL, i | O_NONBLOCK))
	{
		close(lsock);
		lsock = -1;
		fprintf(stderr, "Unable to set socket nonblocking (lsock)\n");
		return 0;
	}
	
	i = 1;
	if(-1 == setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)))
		fprintf(stderr, "Didn't set SO_REUSEADDR (lsock)\n");
	
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(7776);
	sin.sin_addr.s_addr = inet_addr("127.0.0.1"); /* For now until encryption and passwords. */
	
	if(-1 == bind(lsock, (struct sockaddr*)&sin, sizeof(sin)))
	{
		close(lsock);
		lsock = -1;
		fprintf(stderr, "Unable to bind socket (lsock)\n");
		return 0;
	}
	
	if(-1 == listen(lsock, 2))
	{
		close(lsock);
		lsock = -1;
		fprintf(stderr, "Unable to listen socket (lsock)\n");
		return 0;
	}
	
	socket_get_all(_sockstart, NULL);
	
	return 1; /* Yes, loaded. */
}


/* action:
	0 = normal
	1 = load
	2 = unload
*/
int service_run(int action)
{
	if(0 == action)
	{
		return serv();
	}
	else if(1 == action)
	{
		/* Load... */
		
		return do_load();
	}
	else if(2 == action)
	{
		/* Unload... */
		
		if(-1 != lsock)
			close(lsock);
		lsock = -1;
		
		socket_get_all(_sockstop, NULL);
		
		return 0;
	}
}

